package com.gulimall.product.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.gulimall.common.core.domain.Result;
import com.gulimall.common.vo.product.Catalogs2Vo;
import com.gulimall.product.domain.entity.Category;
import com.gulimall.product.mapper.CategoryMapper;
import com.gulimall.product.service.ICategoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.gulimall.common.constant.NumberConstants.ZERO;
import static com.gulimall.common.constant.RedisConstants.CATALOG_JSON;

/**
 * <p>
 * 商品三级分类 服务实现类
 * </p>
 *
 * @author 坤哥
 * @since 2025-08-25
 */
@Slf4j
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements ICategoryService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    /**
     * 查询所有分类及子分类
     */
    @Override
    public List<Category> getProductCategory() {
        //查询全部商品
        List<Category> categories = list();
        //找父分类id=0的商品
        List<Category> collect = categories.stream().filter(category -> {
            return Objects.equals(category.getParentCid(), ZERO);
        }).map(category->{
            category.setChildren(productClassify(category,categories));//category==各个顶级分类;categories==全部商品
            return category;
        }).sorted((menu1,menu2)->{ //对集合中的所有元素进行升序排序,如果返回值 > 0，说明 menu1 排在 menu2 后面。如果返回值 < 0，说明 menu1 排在 menu2 前面。
            return menu1.getSort() - menu2.getSort();
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 递归实现商品分类
     * 总共三级分类，所以只调用两次productClassify()即可
     */
    public List<Category> productClassify(Category root,List<Category> all){
        List<Category> collect = all.stream().filter(category -> {
            return Objects.equals(category.getParentCid(), root.getCatId());
        }).map(category -> {
            category.setChildren(productClassify(category, all));//category1==各个父类;categories==全部商品
            return category;
        }).sorted((menu1,menu2)->{
            return menu1.getSort() - menu2.getSort();
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 批量或单个删除商品
     */
    @Override
    public Result deleteProduct(List<Long> catIds) {
        // TODO 检查菜单是否被其他地方引用
        boolean success = removeBatchByIds(catIds);
        if(success){
            return Result.ok();
        }
        return Result.fail();
    }

    /**
     * 查询一级分类
     */
    @Override
    //cache:category::Level1Categorys
    @Cacheable(value = {"cache:category"},key = "'Level1Categorys'") //把这个方法的返回结果存入redis，缓存起来
    public List<Category> getLevel1Categorys() {
        List<Category> categoryList = lambdaQuery().eq(Category::getCatLevel, 1).list();
        return categoryList;
    }

    /**
     * 从redis中查询缓存数据
     * 解决下面的问题：
     * 缓存穿透
     * 缓存雪崩
     * 缓存击穿
     */
    public Map<String, List<Catalogs2Vo>> getCatalogJson() throws InterruptedException {
        //查redis
        String json = stringRedisTemplate.opsForValue().get(CATALOG_JSON);
        if (json == null){//redis中没有数据,就要获取锁去查数据库，更新redis的数据
            RLock lock = redissonClient.getLock("catalogLock");//给锁起个名字
            //只让一个线程去更新数据，其他的线程则等待
            //尝试获取锁
            boolean success = lock.tryLock();
            try {
                //从数据库中查询数据
                if(Boolean.TRUE.equals(success)){//拿到锁，更新数据库
                    Map<String, List<Catalogs2Vo>> catalogJsonWithDB = getCatalogJsonWithDB();
                    //如果数据库中也没查到数据，这就可能造成缓存穿透
                    //把空值放入redis
                    if(CollectionUtils.isEmpty(catalogJsonWithDB)){
                        stringRedisTemplate.opsForValue().set(CATALOG_JSON,"");
                        return new HashMap<>();
                    }
                    //把数据保存到redis
                    //设置随机过期时间，避免雪崩
                    stringRedisTemplate.opsForValue().set(CATALOG_JSON, JSONObject.toJSONString(catalogJsonWithDB),1, TimeUnit.HOURS);
                    //返回数据
                    return catalogJsonWithDB;
                }
            } finally {
                if (success && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
            Thread.sleep(200);
            getCatalogJson();//再次尝试查询，如果在redis中查到数据，则跳过拿锁的过程
        }
        //redis中有数据
        if(Objects.equals(json,"")){
            return new HashMap<>();
        }
        Map<String, List<Catalogs2Vo>> result = JSONObject.parseObject(json, new TypeReference<Map<String, List<Catalogs2Vo>>>() {});
        return result;
    }

    /**
     * 从数据库中查询数据
     */
    public Map<String, List<Catalogs2Vo>> getCatalogJsonWithDB() {
        log.info("查询数据库");
        // 性能优化：将数据库的多次查询变为一次
        List<Category> selectList = this.baseMapper.selectList(null);

        //1、查出所有分类
        //1、1）查出所有一级分类
        List<Category> level1Categories = getParentCid(selectList, 0L);

        //封装数据
        Map<String, List<Catalogs2Vo>> parentCid = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1、每一个的一级分类,查到这个一级分类的二级分类
            List<Category> categoryEntities = getParentCid(selectList, v.getCatId());

            //2、封装上面的结果
            List<Catalogs2Vo> catalogs2Vos = null;
            if (categoryEntities != null) {
                catalogs2Vos = categoryEntities.stream().map(l2 -> {
                    Catalogs2Vo catalogs2Vo = new Catalogs2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName().toString());

                    //1、找当前二级分类的三级分类封装成vo
                    List<Category> level3Catelog = getParentCid(selectList, l2.getCatId());

                    if (level3Catelog != null) {
                        List<Catalogs2Vo.Catalog3Vo> category3Vos = level3Catelog.stream().map(l3 -> {
                            //2、封装成指定格式
                            Catalogs2Vo.Catalog3Vo category3Vo = new Catalogs2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());

                            return category3Vo;
                        }).collect(Collectors.toList());
                        catalogs2Vo.setCatalog3List(category3Vos);
                    }

                    return catalogs2Vo;
                }).collect(Collectors.toList());
            }

            return catalogs2Vos;
        }));

        return parentCid;
    }

    private List<Category> getParentCid(List<Category> selectList, Long parentCid) {
        return selectList.stream().filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());
    }
}
